<?php

declare(strict_types=1);

namespace App\Controller;

use App\Model\EnergyIotLicense;
use App\Model\EnergyIotLicenseAuthId;
use App\Service\Cryptor;
use Carbon\Carbon;
use Exception;
use Hyperf\Di\Annotation\Inject;
use Hyperf\HttpServer\Annotation\Controller;
use Hyperf\HttpServer\Annotation\PostMapping;
use ParagonIE\Halite\Alerts\CannotPerformOperation;
use ParagonIE\Halite\Alerts\InvalidDigestLength;
use ParagonIE\Halite\Alerts\InvalidMessage;
use ParagonIE\Halite\Alerts\InvalidType;
use SodiumException;
use Symfony\Component\Finder\Finder;
use Xnrcms\BaseTools\Fun;
use Xnrcms\BaseTools\LicenseGenerator;
use Xnrcms\BaseTools\Rsa\Rsa;

use function Hyperf\Support\env;

#[Controller(prefix: '/')]
class Authorize extends AbstractController
{
    #[Inject]
    private Cryptor $cryptor;

    /**
     * 激活许可证授权.
     * @return string[]
     * @throws CannotPerformOperation
     * @throws InvalidDigestLength
     * @throws InvalidMessage
     * @throws InvalidType
     * @throws SodiumException
     */
    #[PostMapping('activate')]
    public function activate(): array
    {
        $data = $this->request->all();

        // 参数缺失检验
        foreach (['license_no', 'signature', 'timestamp', 'uuid', 'license_auth_id', 'license_auth_key'] as $value) {
            $data[$value] = $data[$value] ?? '';
            if (empty($data[$value])) {
                return ['error' => $value . ' does not exist'];
            }
        }

        // 授权信息检测
        $model = EnergyIotLicenseAuthId::query();
        $authInfo = $model->where([
            ['license_auth_id', '=', $data['license_auth_id']],
            ['uuid', '=', $data['uuid']],
        ])->first(['id', 'pub_key', 'status']);

        if (empty($authInfo)) {
            return ['error' => 'license_auth_id does not exist'];
        }

        if ($authInfo->getAttributeValue('status') !== 1) {
            return ['error' => 'license_auth_id not enabled'];
        }

        $signature = $data['signature'];
        $pubKey = $authInfo->getAttributeValue('pub_key', '');

        // 剔除公钥和签名
        unset($data['signature']);

        // 验签
        if ((new Rsa())->verifyRsaSign($data, $signature, $pubKey) === false) {
            return ['error' => 'signature error'];
        }

        // 检验系统时间是否与服务器时间有差异（误差+=5分钟）
        $diff = Carbon::parse($data['timestamp'])->diffInSeconds();
        if ($diff >= 300) {
            return ['error' => 'timestamp error'];
        }

        if (! LicenseGenerator::validate($data['license_no'])) {
            return ['error' => 'license_no error'];
        }

        $license = EnergyIotLicense::whereNull('deleted_at')->where([
            ['license_no', '=', $data['license_no']],
        ])->first();

        if (empty($license)) {
            return ['error' => 'license_no does not exist'];
        }

        if ($license->getAttributeValue('activate_status') === 1) {
            return ['error' => 'the license has already been used'];
        }

        // 有效期错误不允许激活
        $expirationDate = $license->getAttributeValue('expiration_date');
        if (empty($expirationDate) || Carbon::now()->diffInSeconds($expirationDate, false) <= 0) {
            return ['error' => 'license validity period error'];
        }

        // 激活时间
        $activateTime = $license->getAttributeValue('activate_time');
        $activateTime = ! empty($activateTime) ? $activateTime : Carbon::now()->format('Y-m-d H:i:s');

        $priKey = file_get_contents(BASE_PATH . '/cert/platform_private.key');
        $licenseData = [
            'file_auth' => $this->getHash(['uuid' => $data['uuid']]),
            'expiration_date' => $expirationDate,
            'uuid' => $data['uuid'],
            'activate_time' => $activateTime,
            'key' => $license->getAttributeValue('license_no'),
        ];

        $signature = (new Rsa())->rsaPriSign($licenseData, $priKey);

        $license->setAttribute('activate_time', $activateTime);
        $license->setAttribute('activate_status', 1);
        $license->setAttribute('license_auth_id', $data['license_auth_id']);
        $license->setAttribute('license_auth_key', $data['license_auth_key']);
        $license->setAttribute('license', $this->cryptor->encryptLicense(array_merge($licenseData, ['signature' => $signature])));
        $license->save();

        unset($license);

        return array_merge($licenseData, ['signature' => $signature]);
    }

    /**
     * 在线初始化授权ID.
     * @return string[]
     * @throws Exception
     */
    #[PostMapping('initId')]
    public function initId(): array
    {
        $data = $this->request->all();

        // 参数缺失检验
        foreach (['timestamp', 'uuid'] as $value) {
            $data[$value] = $data[$value] ?? '';
            if (empty($data[$value])) {
                return ['error' => $value . ' does not exist'];
            }
        }

        // 检验系统时间是否与服务器时间有差异（误差+=5分钟）
        $diff = Carbon::parse($data['timestamp'])->diffInSeconds();
        if ($diff >= 300) {
            return ['error' => 'timestamp error'];
        }

        // 根据uuid检测是否已经存在，存在直接返回
        $model = EnergyIotLicenseAuthId::query();
        $authId = $model->whereNull('deleted_at')->where([
            ['uuid', '=', $data['uuid']],
        ])->value('id');

        $now = Carbon::now();

        if ($authId <= 0) {
            $rsa = (new Rsa())->createRsa();
            $authId = $model->insertGetId([
                'license_auth_id' => $now->copy()->format('YmdHi') . Fun::randomString(4),
                'license_auth_key' => md5(base64_encode(random_bytes(32))),
                'pub_key' => $rsa['pubKey'] ?? '',
                'pri_key' => $rsa['priKey'] ?? '',
                'uuid' => $data['uuid'],
                'status' => 0,
                'created_at' => $now->copy()->format('Y-m-d H:i:s'),
                'updated_at' => $now->copy()->format('Y-m-d H:i:s'),
            ]);
        } else {
            $model->where('id', $authId)->update(['status' => 0, 'updated_at' => $now->copy()->format('Y-m-d H:i:s')]);
        }

        $authInfo = $model->where('id', $authId)->first(['id', 'license_auth_id', 'license_auth_key', 'pub_key', 'pri_key', 'uuid'])->toArray();

        $pubKey = file_get_contents(BASE_PATH . '/cert/platform_public.key');

        return array_merge($authInfo, ['platform_public_key' => $pubKey]);
    }

    /**
     * 生成本地许可证
     * @return string[]
     * @throws CannotPerformOperation
     * @throws InvalidDigestLength
     * @throws InvalidMessage
     * @throws InvalidType
     * @throws SodiumException
     */
    #[PostMapping('createLocalLicense')]
    public function createLocalLicense()
    {
        $data = $this->request->all();

        // 参数缺失检验
        foreach (['license_auth_id', 'license_no'] as $value) {
            $data[$value] = $data[$value] ?? '';
            if (empty($data[$value])) {
                return ['error' => $value . ' does not exist'];
            }
        }

        $licenseAuthId = $data['license_auth_id'];
        $licenseNo = $data['license_no'];

        // 根据许可证授权ID寻找近期最新的一条许可证数据，获取生成许可证的必要数据
        $lastLicense = EnergyIotLicense::whereNull('deleted_at')->where('license_auth_id', $licenseAuthId)->orderBy('activate_time', 'desc')->value('license');
        if (empty($lastLicense)) {
            return ['error' => '该授权ID没有进行首次联网激活，不能生成许可证'];
        }

        // 根据许可证授权ID 获取授权ID的密钥信息
        $authId = EnergyIotLicenseAuthId::whereNull('deleted_at')->where('license_auth_id', $licenseAuthId)->first(['id', 'uuid', 'license_auth_id', 'license_auth_key', 'pri_key']);
        if (empty($authId)) {
            return ['error' => '该授权ID信息不存在'];
        }

        // 获取当前许可证数据
        $license = EnergyIotLicense::whereNull('deleted_at')->where('license_no', $licenseNo)->first(['id', 'activate_status', 'license_no', 'expiration_date', 'activate_time']);

        if (empty($license)) {
            return ['error' => 'license_no does not exist'];
        }

        if ($license->getAttributeValue('activate_status') === 1) {
            return ['error' => 'the license has already been used'];
        }

        // 有效期错误不允许激活
        $expirationDate = $license->getAttributeValue('expiration_date');
        if (empty($expirationDate) || Carbon::now()->diffInSeconds($expirationDate, false) <= 0) {
            return ['error' => 'license validity period error'];
        }

        // 激活时间
        $activateTime = $license->getAttributeValue('activate_time');
        $activateTime = ! empty($activateTime) ? $activateTime : Carbon::now()->format('Y-m-d H:i:s');

        $lastLicense = $this->cryptor->decryptLicense($lastLicense);
        $priKey = file_get_contents(BASE_PATH . '/cert/platform_private.key');
        $licenseData = [
            'file_auth' => $lastLicense['file_auth'],
            'expiration_date' => $expirationDate,
            'uuid' => $lastLicense['uuid'],
            'activate_time' => $activateTime,
            'key' => $license->getAttributeValue('license_no'),
        ];

        $signature = (new Rsa())->rsaPriSign($licenseData, $priKey);

        $license->setAttribute('activate_time', $activateTime);
        $license->setAttribute('activate_status', 1);
        $license->setAttribute('license_auth_id', $authId->getAttributeValue('license_auth_id'));
        $license->setAttribute('license_auth_key', $authId->getAttributeValue('license_auth_key'));
        $license->setAttribute('license', $this->cryptor->encryptLicense(array_merge($licenseData, ['signature' => $signature])));
        $license->save();

        unset($license);

        return array_merge($licenseData, ['signature' => $signature, 'success' => 'ok']);
    }

    public function getHash(array $param): string
    {
        $hashes = [];
        $path = '/' . trim(env('AUTHORIZE_ROOT_FILE_DIF', BASE_PATH), '/');

        // 遍历需要校验的文件
        $dir = [
            $path . '/app/AsyncQueue',
            $path . '/app/Iot',
            $path . '/app/Auth',
            $path . '/app/Power',
            $path . '/bin',
            $path . '/app/Tcp',
            $path . '/config',
        ];

        $files = Finder::create();
        foreach ($dir as $value) {
            foreach ($files->in($value)->name('*.php')->files() as $file) {
                $relativePath = str_replace($path . '/', '', $file->getPathname());
                $hashes[$relativePath] = hash_file('sha256', $file->getRealPath());
            }
        }

        ksort($hashes);

        return md5(json_encode(array_merge($hashes, $param), JSON_PRETTY_PRINT));
    }

    /**
     * 生成许可证号.
     * @return array|string[]
     * @throws Exception
     */
    #[PostMapping('create')]
    public function create(): array
    {
        $data = $this->request->all();
        // 有效期判断
        $expirationDate = $data['expiration_date'] ?? '';
        if (empty($expirationDate)) {
            return ['error' => '有效期不能为空'];
        }

        $nowTime = Carbon::now()->format('Y-m-d H:i:s');

        // 有效期必须不能是一个过期的时间
        $expirationDate = Carbon::parse($expirationDate)->format('Y-m-d 23:59:59');
        if (Carbon::now()->diffInSeconds($expirationDate, false) <= 0) {
            return ['error' => '有效期必须大于当前时间'];
        }

        $id = EnergyIotLicense::insertGetId([
            'license_no' => LicenseGenerator::generate(),
            'expiration_date' => $expirationDate,
            'activate_status' => 0,
            'created_at' => $nowTime,
            'updated_at' => $nowTime,
            'created_by' => $data['created_by'] ?? 0,
            'remark' => $data['remark'] ?? '',
        ]);

        return ['success' => 'ok', 'data' => $id];
    }

    /**
     * 获取许可证信息.
     * @return array|string[]
     */
    private function licenseInfo(string $license_no): array
    {
        $license = EnergyIotLicense::whereNull('deleted_at')->where([
            ['license_no', '=', $license_no],
        ])->first(['id', 'activate_status', 'expiration_date']);

        if (empty($license)) {
            return ['error' => 'license_no does not exist'];
        }

        if ($license->getAttributeValue('activate_status') === 1) {
            return ['error' => 'the license has already been used'];
        }

        // 有效期错误不允许激活
        $expirationDate = $license->getAttributeValue('expiration_date');
        if (empty($expirationDate) || Carbon::now()->diffInSeconds($expirationDate, false) <= 0) {
            return ['error' => 'license validity period error'];
        }

        return [
            'license_no' => $license->getAttributeValue('expiration_date'),
            'expiration_date' => $expirationDate,
        ];
    }
}
